/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package com.wzhq.pacsai;

import com.jfinal.plugin.activerecord.Db;
import com.jfinal.plugin.activerecord.Record;
import com.wzhq.commTencent.PostDcm;
import com.wzhq.commTencent.PostTencent;
import com.wzhq.model.MainImages;
import com.wzhq.model.SyncAIStatus;
import com.wzhq.util.JsfUtil;
import java.io.File;
import java.io.IOException;
import java.sql.Timestamp;
import java.util.Calendar;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Properties;
import java.util.ResourceBundle;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.logging.Level;

import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.OptionBuilder;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import org.dcm4che3.data.Tag;
import org.dcm4che3.data.Attributes;
import org.dcm4che3.data.UID;
import org.dcm4che3.data.VR;
import org.dcm4che3.io.DicomInputStream;
import org.dcm4che3.io.DicomOutputStream;
import org.dcm4che3.net.ApplicationEntity;
import org.dcm4che3.net.Association;
import org.dcm4che3.net.Connection;
import org.dcm4che3.net.Device;
import org.dcm4che3.net.PDVInputStream;
import org.dcm4che3.net.Status;
import org.dcm4che3.net.TransferCapability;
import org.dcm4che3.net.pdu.PresentationContext;
import org.dcm4che3.net.service.BasicCEchoSCP;
import org.dcm4che3.net.service.BasicCStoreSCP;
import org.dcm4che3.net.service.DicomServiceException;
import org.dcm4che3.net.service.DicomServiceRegistry;
import org.dcm4che3.tool.common.CLIUtils;
import org.dcm4che3.util.AttributesFormat;
import org.dcm4che3.util.SafeClose;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 *
 * @author wzhq
 */
public class PacsAI implements Runnable {

    private static final Logger LOG = LoggerFactory.getLogger(PacsAI.class);

    private static ResourceBundle rb
            = ResourceBundle.getBundle("com.wzhq.pacsai.messages");
    private static final String PART_EXT = ".part";

    private final Device device = new Device("storescp");
    private final ApplicationEntity ae = new ApplicationEntity("*");
    private final Connection conn = new Connection();
    private File storageDir;
    private AttributesFormat filePathFormat;
    private int status;
    private static String StudyInstanceUID;
    public String jobName;

    public static String getStudyInstanceUID() {
        return StudyInstanceUID;
    }

    public static void setStudyInstanceUID(String StudyInstanceUID) {
        PacsAI.StudyInstanceUID = StudyInstanceUID;
    }
    private final BasicCStoreSCP cstoreSCP = new BasicCStoreSCP("*") {

        @Override
        protected void store(Association as, PresentationContext pc,
                Attributes rq, PDVInputStream data, Attributes rsp)
                throws IOException {
            rsp.setInt(Tag.Status, VR.US, status);
            if (storageDir == null) {
                return;
            }

            String cuid = rq.getString(Tag.AffectedSOPClassUID);
            String iuid = rq.getString(Tag.AffectedSOPInstanceUID);
            String tsuid = pc.getTransferSyntax();
            File file = new File(storageDir, iuid + PART_EXT);
            try {
                storeTo(as, as.createFileMetaInformation(iuid, cuid, tsuid),
                        data, file);
                renameTo(as, file, new File(storageDir, filePathFormat == null ? iuid : filePathFormat.format(parse(file))));
            } catch (Exception e) {
                deleteFile(as, file);
                throw new DicomServiceException(Status.ProcessingFailure, e);
            }
        }

    };

    public PacsAI() throws IOException {
        device.setDimseRQHandler(createServiceRegistry());
        device.addConnection(conn);
        device.addApplicationEntity(ae);
        ae.setAssociationAcceptor(true);
        ae.addConnection(conn);
    }

    public PacsAI(String jobName) {
        this.jobName = jobName;
    }

    private void storeTo(Association as, Attributes fmi,
            PDVInputStream data, File file) throws IOException {
        LOG.info("{}: M-WRITE {}", as, file);
        file.getParentFile().mkdirs();
        DicomOutputStream out = new DicomOutputStream(file);
        try {
            out.writeFileMetaInformation(fmi);
            data.copyTo(out);
        } finally {
            SafeClose.close(out);
        }
    }

    private static void renameTo(Association as, File from, File dest)
            throws IOException {
        LOG.info("{}: M-RENAME {} to {}", as, from, dest);
        if (!dest.getParentFile().mkdirs()) {
            dest.delete();
        }
        if (!from.renameTo(dest)) {
            PostTencent.ERRDCMOPERFLAG = 1;
            LOG.error("Failed to rename " + from + " to " + dest);
            throw new IOException("Failed to rename " + from + " to " + dest);
        }

        DicomUtil dicomFileInfo = new DicomUtil(dest);
        Integer DownCounts = 1;

        String StudyInstanceUID = dicomFileInfo.getValueByTag(Tag.StudyInstanceUID);
        String SeriesInstanceUID = dicomFileInfo.getValueByTag(Tag.SeriesInstanceUID);
        String SOPInstanceUID = dicomFileInfo.getValueByTag(Tag.SOPInstanceUID);
        LOG.info("+++++" + StudyInstanceUID + "|||" + SeriesInstanceUID + "|||" + SeriesInstanceUID);
        Record MainImagesFlag = Db.findById("MainImages", "StudyInstanceUID,SeriesInstanceUID,SOPInstanceUID",
                StudyInstanceUID, SeriesInstanceUID, SOPInstanceUID);
        if (MainImagesFlag == null) {
            DownCounts = 1;
        } else {
            DownCounts = MainImagesFlag.getInt("DownCounts") + 1;
        }

        try {
            Record MainImages = new Record();
            MainImages.set("StudyInstanceUID", StudyInstanceUID).
                    set("SeriesInstanceUID", SeriesInstanceUID).
                    set("SOPInstanceUID", SOPInstanceUID).
                    set("AccessionNumber", dicomFileInfo.getValueByTag(Tag.AccessionNumber)).
                    set("PatientID", dicomFileInfo.getValueByTag(Tag.PatientID)).
                    set("PatientBirthDate", dicomFileInfo.getValueByTag(Tag.PatientBirthDate)).
                    set("PatientSex", dicomFileInfo.getValueByTag(Tag.PatientSex)).
                    set("PatientName", dicomFileInfo.getValueByTag(Tag.PatientName)).
                    set("BodyPartExamined", dicomFileInfo.getValueByTag(Tag.BodyPartExamined)).
                    set("StudyDate", dicomFileInfo.getValueByTag(Tag.StudyDate)).
                    set("DownCounts", DownCounts).
                    set("ImageArrivedTime", JsfUtil.getDateTime()).
                    set("FilePath", dest.getParent()).
                    set("FileName", dest.getName()).
                    set("FileSize", DicomUtil.getFileSize(dest));

            if (MainImagesFlag == null) {
                Db.save("MainImages", "StudyInstanceUID,SeriesInstanceUID,SOPInstanceUID", MainImages);
            } else {
                Db.update("MainImages", "StudyInstanceUID,SeriesInstanceUID,SOPInstanceUID", MainImages);
            }
        } catch (Exception e) {
            PostTencent.ERRDCMOPERFLAG = 1;
            LOG.info("插入MainImages表错误" + StudyInstanceUID + e);
            throw e;
        }
    }

    private static String parsePath(File file) {
        StringBuilder sb = new StringBuilder();
        try {
            File temp = file;
            while (temp.getParentFile() != null && temp.getParentFile().getName().length() != 0) {
                sb.insert(0, "/" + temp.getParentFile().getName());
                temp = temp.getParentFile();
            }
            sb.append("/");
        } catch (Exception e) {
            LOG.warn("parsePath failed!", file.getAbsolutePath());
        }
        return sb.toString();
    }

    private static Attributes parse(File file) throws IOException {
        DicomInputStream in = new DicomInputStream(file);
        try {
            in.setIncludeBulkData(DicomInputStream.IncludeBulkData.NO);
            return in.readDataset(-1, Tag.PixelData);
        } finally {
            SafeClose.close(in);
        }
    }

    private static void deleteFile(Association as, File file) {
        if (file.delete()) {
            LOG.info("{}: M-DELETE {}", as, file);
        } else {
            LOG.warn("{}: M-DELETE {} failed!", as, file);
        }
    }

    private DicomServiceRegistry createServiceRegistry() {
        DicomServiceRegistry serviceRegistry = new DicomServiceRegistry();
        serviceRegistry.addDicomService(new BasicCEchoSCP());
        serviceRegistry.addDicomService(cstoreSCP);
        return serviceRegistry;
    }

    public void setStorageDirectory(File storageDir) {
        if (storageDir != null) {
            storageDir.mkdirs();
        }
        this.storageDir = storageDir;
    }

    public void setStorageFilePathFormat(String pattern) {
        this.filePathFormat = new AttributesFormat(pattern);
    }

    public void setStatus(int status) {
        this.status = status;
    }

    private static CommandLine parseComandLine(String[] args)
            throws ParseException {
        Options opts = new Options();
        CLIUtils.addBindServerOption(opts);
        CLIUtils.addAEOptions(opts);
        CLIUtils.addCommonOptions(opts);
        addStatusOption(opts);
        addStorageDirectoryOptions(opts);
        addTransferCapabilityOptions(opts);
        return CLIUtils.parseComandLine(args, opts, rb, PacsAI.class);
    }

    @SuppressWarnings("static-access")
    private static void addStatusOption(Options opts) {
        opts.addOption(OptionBuilder
                .hasArg()
                .withArgName("code")
                .withDescription(rb.getString("status"))
                .withLongOpt("status")
                .create(null));
    }

    @SuppressWarnings("static-access")
    private static void addStorageDirectoryOptions(Options opts) {
        opts.addOption(null, "ignore", false,
                rb.getString("ignore"));
        opts.addOption(OptionBuilder
                .hasArg()
                .withArgName("path")
                .withDescription(rb.getString("directory"))
                .withLongOpt("directory")
                .create(null));
        opts.addOption(OptionBuilder
                .hasArg()
                .withArgName("pattern")
                .withDescription(rb.getString("filepath"))
                .withLongOpt("filepath")
                .create(null));
    }

    @SuppressWarnings("static-access")
    private static void addTransferCapabilityOptions(Options opts) {
        opts.addOption(null, "accept-unknown", false,
                rb.getString("accept-unknown"));
        opts.addOption(OptionBuilder
                .hasArg()
                .withArgName("file|url")
                .withDescription(rb.getString("sop-classes"))
                .withLongOpt("sop-classes")
                .create(null));
    }

    private static String[] fromatInput(HashMap args) {
        Set set = args.entrySet();
        Iterator iterator = set.iterator();
        String[] msArgs = new String[set.size() * 2];
        for (int i = 0; iterator.hasNext(); i = i + 2) {
            Map.Entry entry = (Map.Entry) iterator.next();
            String key = (String) entry.getKey();
            String value = (String) entry.getValue();
            msArgs[i] = key;
            msArgs[i + 1] = value;
            LOG.info("InputArgs:" + key + "  " + value);
            System.out.println(key + " " + value);
        }
        return msArgs;
    }

    private boolean queryDcmDcm4che3(String[] msArgs) {
        boolean isErr = false;
        try {
            MoveSCU queryDcm = new MoveSCU();
            queryDcm.findDcmMoveSCU(msArgs);
        } catch (IOException ex) {
            isErr = true;
            LOG.error("scu向PACS服务器查询错误1");
            LOG.error(ex.toString());
            System.err.println("MoveSCU: " + ex.getMessage());
            ex.printStackTrace();
        } catch (Exception e) {
            isErr = true;
            LOG.error("scu向PACS服务器查询错误2");
            LOG.error(e.toString());
            System.err.println("MoveSCU: " + e.getMessage());
            e.printStackTrace();
        }
        return isErr;
    }

    private static void queryDcmDcm4che2(String[] msArgs) {
        try {
            // DcmQR queryDcm = new DcmQR(msArgs);
            // queryDcm.findDcmMoveSCU(msArgs);
        } catch (Exception e) {
            System.err.println("DcmQR: " + e.getMessage());
            e.printStackTrace();
            System.exit(2);
        }
    }

    public boolean retrieveDcm(String[] args) {
        boolean isError = false;
        try {
            CommandLine cl = parseComandLine(args);
            PacsAI main = new PacsAI();
            CLIUtils.configureBindServer(main.conn, main.ae, cl);
            CLIUtils.configure(main.conn, cl);
            main.setStatus(CLIUtils.getIntOption(cl, "status", 0));
            configureTransferCapability(main.ae, cl);
            configureStorageDirectory(main, cl);
            ExecutorService executorService = Executors.newCachedThreadPool();
            ScheduledExecutorService scheduledExecutorService
                    = Executors.newSingleThreadScheduledExecutor();
            main.device.setScheduledExecutor(scheduledExecutorService);
            main.device.setExecutor(executorService);
            main.device.bindConnections();
        } catch (ParseException e) {
            isError = true;
            LOG.error("启动图像接收服务错误Exception:");
            LOG.error(e.toString());
            System.err.println("storescp: " + e.getMessage());
            System.err.println(rb.getString("try"));

            System.exit(2);
        } catch (Exception e) {
            isError = true;
            LOG.error("启动图像接收服务错误Exception:");
            LOG.error(e.toString());
            System.err.println("storescp: " + e.getMessage());
            e.printStackTrace();
            System.exit(2);
        }
        return isError;
    }

    public static HashMap initQueryState(String moveScuMatchKeySUID) {
        //-b MOVESCU: 11133 - c AE_ARCH1@192.168.2.63:104 --dest MOVESCU -m StudyInstanceUID = 1.2.840.113619.186.1441772842175112.20180117144250009.393
        String moveScuBindAet = "MOVESCU:11133";
        String moveScuConnServer = "AE_ARCH1@192.168.2.63:104";
        String moveScuDestAet = "MOVESCU";
        //String moveScuMatchKeySUID = "1.2.840.113704.1.111.3140.1516585787.14";
        HashMap moveSCUArgs = new HashMap();
        moveSCUArgs.put("-b", moveScuBindAet);
        moveSCUArgs.put("-c", moveScuConnServer);
        moveSCUArgs.put("--dest", moveScuDestAet);
        moveSCUArgs.put("-m", "StudyInstanceUID=" + moveScuMatchKeySUID);
        return moveSCUArgs;
    }

    public static void main(String[] args) throws InterruptedException {

        MainConfig DatabaseConn = new MainConfig();
        DatabaseConn.configPlugin();

        //dcmqr  -L MOVESCU AE_ARCH1@192.168.2.63:104  -q AccessionNumber=ZH171231DR039  -cmove MOVESCU
        String dcmqrBindAet = "MOVESCU";
        String dcmqrConnServer = "AE_ARCH1@192.168.2.63:104";
        String dcmqrDestAet = "MOVESCU";
        String dcmqrAccessionNumber = "ZH171231DR039";
        HashMap dcmqrArgs = new HashMap();
        //dcmqrArgs.put("d", dcmqrConnServer);
        dcmqrArgs.put("-L", dcmqrBindAet + " " + dcmqrConnServer);
        dcmqrArgs.put("-cmove", dcmqrDestAet);
        dcmqrArgs.put("-q", "AccessionNumber=" + dcmqrAccessionNumber);

        //-b MOVESCU:104 --sop-classes c:\aaa\sop-classes.properties --directory c:\aaa
        String storeScpBindAet = "MOVESCU:104";
        String storeScpSopClass = ClassLoader.getSystemResource("") + "sop-classes.properties";
        String storeScpDirectory = "c:\\aaa";
        HashMap storeScpArgs = new HashMap();
        storeScpArgs.put("-b", storeScpBindAet);
        storeScpArgs.put("--sop-classes", storeScpSopClass);
        storeScpArgs.put("--directory", storeScpDirectory);

        //retrieveDcm(fromatInput(storeScpArgs));
        String StudyInstanceUID;
        for (int i = 1; i < 100; i++) {

            long start = System.currentTimeMillis();
            Thread.sleep(5000);
            long end = System.currentTimeMillis();
            System.out.println(end - start + "ms");

            //从同步表读待同步SUID
            StudyInstanceUID = readSUID2AI();
            if (StudyInstanceUID != null) {

                setStudyInstanceUID(StudyInstanceUID);
                //deleteStudyDb(getStudyInstanceUID());

                //queryDcmDcm4che3(fromatInput(initQueryState(StudyInstanceUID)));
                //queryDcmDcm4che2(fromatInput(dcmqrArgs));
                writeSyncStatus(StudyInstanceUID);
            }
        }
    }

    //从同步表下载影像
    public boolean downImage2Pacs() {
        boolean isError = false;
        String StudyInstanceUID;
        try {
            //从同步表读待同步SUID
            StudyInstanceUID = readSUID2AI();

            //null表示没有新记录可同步
            if (StudyInstanceUID != null) {
                //锁该条记录
                PostDcm.lockUsingFlag(StudyInstanceUID, 1);
                setStudyInstanceUID(StudyInstanceUID);
                //deleteStudyDb(getStudyInstanceUID());
                isError = queryDcmDcm4che3(fromatInput(initQueryState(StudyInstanceUID)));
                //queryDcmDcm4che2(fromatInput(dcmqrArgs));
                //写同步表已同步标记
                writeSyncStatus(StudyInstanceUID);
                //解锁该条记录
                PostDcm.lockUsingFlag(StudyInstanceUID, 0);
            }
        } catch (Exception ex) {
            isError = true;
            PostTencent.ERRPOSTOPERFLAG = 1;
            LOG.error("从同步表下载影像错误:downImage2Pacs");
            LOG.error(ex.toString());
        }
        return isError;
    }

    //启动DICOM接收服务
    public boolean startScpService() {
        boolean isError = false;
        //-b MOVESCU:104 --sop-classes c:\aaa\sop-classes.properties --directory c:\aaa
        String storeScpBindAet = "MOVESCU:104";
        String storeScpSopClass = ClassLoader.getSystemResource("") + "sop-classes.properties";
        String storeScpDirectory = PostTencent.STORESCPDIR;
        HashMap storeScpArgs = new HashMap();
        storeScpArgs.put("-b", storeScpBindAet);
        storeScpArgs.put("--sop-classes", storeScpSopClass);
        storeScpArgs.put("--directory", storeScpDirectory);

        isError = retrieveDcm(fromatInput(storeScpArgs));
        return isError;
    }

    //写同步表已同步标记
    private static void writeSyncStatus(String StudyInstanceUID) {
        int counts;
        int SyncFlag;
        Record syncAIStatus = new Record();
        syncAIStatus = Db.findById("syncAIStatus", "StudyInstanceUID", StudyInstanceUID);
        // SyncAIStatus.dao.find("select SyncFlag from SyncAIStatus where StudyInstanceUID=?", StudyInstanceUID);
        SyncFlag = syncAIStatus.getInt("SyncFlag");
        SyncFlag++;
        syncAIStatus = Db.findById("syncAIStatus", "StudyInstanceUID", StudyInstanceUID).set("SyncFlag", SyncFlag);
        Db.update("syncAIStatus", "StudyInstanceUID", syncAIStatus);
    }

    //从同步表读待同步SUID
    private static String readSUID2AI() {
        boolean isErr = false;
        String StudyInstanceUID = null;
        Record syncAIStatus = new Record();
        try {
            syncAIStatus = Db.findFirst("select * from syncAIStatus where  UsingFlag=0 and"
                    + " (SyncFlag=0 or(SyncFlag=2 and Counts<?)) ORDER BY SyncTime LIMIT 1", PostTencent.MAXREOPER);
            if (syncAIStatus == null) {
                StudyInstanceUID = null;
            } else {
                StudyInstanceUID = syncAIStatus.get("StudyInstanceUID");
            }
        } catch (Exception ex) {
            isErr = true;
            PostTencent.ERRPOSTOPERFLAG = 1;
            LOG.error("从同步表读待同步SUID错误" + StudyInstanceUID);
            LOG.error(ex.toString());
        }
        return StudyInstanceUID;
    }

    private static void deleteStudyDb(String StudyInstanceUID) {

        Record MainImages = new Record();
        MainImages = Db.findById("MainImages", "StudyInstanceUID", StudyInstanceUID);
        if (MainImages != null) {
            //Db.delete("MainImages", MainImages);
            Db.delete("delete from MainImages where StudyInstanceUID=?", StudyInstanceUID);
        }
    }

    private void deleteImageDb(String StudyInstanceUID, String SeriesInstanceUID, String SOPInstanceUID) {
        Record MainImages = new Record();
        MainImages = Db.findById("MainImages", "StudyInstanceUID,SeriesInstanceUID,SOPInstanceUID",
                StudyInstanceUID, SeriesInstanceUID, SOPInstanceUID);
        if (MainImages != null) {
            Db.delete("MainImages", "StudyInstanceUID,SeriesInstanceUID,SOPInstanceUID",
                    StudyInstanceUID, SeriesInstanceUID, SOPInstanceUID);
            //Db.delete("delete from MainImages where StudyInstanceUID=?", StudyInstanceUID);
        }
    }

    private static void configureStorageDirectory(PacsAI main, CommandLine cl) {
        if (!cl.hasOption("ignore")) {
            main.setStorageDirectory(
                    new File(cl.getOptionValue("directory", ".")));
            if (cl.hasOption("filepath")) {
                main.setStorageFilePathFormat(cl.getOptionValue("filepath"));
            }
        }
    }

    private static void configureTransferCapability(ApplicationEntity ae,
            CommandLine cl) throws IOException {
        if (cl.hasOption("accept-unknown")) {
            ae.addTransferCapability(
                    new TransferCapability(null,
                            "*",
                            TransferCapability.Role.SCP,
                            "*"));
        } else {
            Properties p = CLIUtils.loadProperties(
                    cl.getOptionValue("sop-classes",
                            "resource:sop-classes.properties"),
                    null);
            for (String cuid : p.stringPropertyNames()) {
                String ts = p.getProperty(cuid);
                //System.out.print(cuid + '\n');
                TransferCapability tc = new TransferCapability(null,
                        CLIUtils.toUID(cuid),
                        TransferCapability.Role.SCP,
                        CLIUtils.toUIDs(ts));
                ae.addTransferCapability(tc);
            }
        }
    }

    public static String toUID(String uid) {
        uid = uid.trim();
        forName(uid);
        return (uid.equals("*") || Character.isDigit(uid.charAt(0)))
                ? uid
                : forName(uid);
    }

    public static String forName(String keyword) {
        try {
            return (String) UID.class.getField(keyword).get(null);
        } catch (Exception e) {
        }
        throw new IllegalArgumentException(keyword);
    }

    @Override
    public void run() {
        boolean isError = false;
        System.out.println(jobName + "启动：" + Calendar.getInstance().get(Calendar.SECOND));
        String msg = downImage2Pacs() ? "出现错误！！" : "正常！";
        //Thread.sleep(2000);
        System.out.println(jobName + " 退出，结果：" + msg + Calendar.getInstance().get(Calendar.SECOND));
    }

}
